package com.xxl.job.core.glue;

import java.math.BigInteger;
import java.security.MessageDigest;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import com.xxl.job.core.handler.IJobHandler;

import groovy.lang.GroovyClassLoader;

/**
 * glue factory, product class/object by name
 * @author xuxueli 2016-1-2 20:02:27
 */
public class GlueFactory {

	private static GlueFactory glueFactory = new GlueFactory();

	public static GlueFactory getInstance() {
		return glueFactory;
	}

	public static void refreshInstance(GlueFactory factory) {
		glueFactory = factory;
	}

	/**
	 * groovy class loader
	 */
	private GroovyClassLoader groovyClassLoader = new GroovyClassLoader();
	private ConcurrentMap<String, Class<?>> CLASS_CACHE = new ConcurrentHashMap<>();

	/**
	 * load new instance, prototype
	 * @param codeSource
	 * @return
	 * @throws Exception
	 */
	public IJobHandler loadNewInstance(String codeSource) throws Exception {
		if (codeSource == null || codeSource.trim().length() <= 0) {
			throw new IllegalArgumentException(">>>>>>>>>>> xxl-glue, loadNewInstance error, codeSource is null");
		}
		Class<?> clazz = getCodeSourceClass(codeSource);
		if (clazz == null) {
			throw new IllegalArgumentException(">>>>>>>>>>> xxl-glue, loadNewInstance error, class is null");
		}
		if (!IJobHandler.class.isAssignableFrom(clazz)) {
			throw new IllegalArgumentException(">>>>>>>>>>> xxl-glue, loadNewInstance error, " + "cannot convert from instance[" + clazz + "] to IJobHandler");
		}
		@SuppressWarnings("unchecked")
		IJobHandler instance = getInstance((Class<IJobHandler>) clazz);
		if (instance == null) {
			throw new IllegalArgumentException(">>>>>>>>>>> xxl-glue, loadNewInstance error, instance is null");
		}
		this.injectService(instance);
		return instance;
	}

	protected Class<?> getCodeSourceClass(String codeSource) {
		try {
			// md5
			byte[] md5 = MessageDigest.getInstance("MD5").digest(codeSource.getBytes());
			String md5Str = new BigInteger(1, md5).toString(16);
			Class<?> clazz = CLASS_CACHE.get(new BigInteger(1, md5).toString(16));
			if (clazz == null) {
				clazz = groovyClassLoader.parseClass(codeSource);
				CLASS_CACHE.putIfAbsent(md5Str, clazz);
			}
			return clazz;
		} catch (Exception e) {
			return groovyClassLoader.parseClass(codeSource);
		}
	}

	/**
	 * inject service of bean field
	 * @param instance
	 */
	protected void injectService(Object instance) {
		// do something
	}

	protected IJobHandler getInstance(Class<IJobHandler> clazz) throws Exception {
		return clazz.newInstance();
	}
}
